package shooter.models;

import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.Serializable;

/**
 * The main sprite. All other models inherit from Sprite.
 * 
 * @author Michail Tausch
 */
public abstract class Sprite implements Serializable, Cloneable {
	double x;
	double y;

	double dx;
	double dy;

	double angle;
	double dangle;

	double scalingFactor;

	int id = 0;

	Polygon boundaryPolygon;

	boolean rotate;
	boolean scalate;
	
	/**
	 * The constructor.
	 * @param id
	 * @param x
	 * @param y
	 */
	public Sprite(int id, double x, double y) {
		this.x = x;
		this.y = y;
		this.id = id;

		dx = 0.01;
		dy = 0.01;

		angle = 0.0;
		dangle = 0.0;

		scalingFactor = 1.0;

		rotate = false;
		scalate = false;
	}

	/**
	 * Returns the x coordinate of the entity.
	 * 
	 * @return the x coordinate
	 */
	public double getX() {
		return x;
	}

	/**
	 * Sets the x coordinate of the entity.
	 * 
	 * @param x
	 *            the x coordinate
	 */
	public void setX(double x) {
		this.x = x;
	}

	/**
	 * Returns the y coordinate of the entity.
	 * 
	 * @return the y coordinate
	 */
	public double getY() {
		return y;
	}

	/**
	 * Sets the y coordinate of the entity.
	 * 
	 * @param y
	 *            the y coordinate
	 */
	public void setY(double y) {
		this.y = y;
	}

	/**
	 * Returns the horizontal movement (dx).
	 * 
	 * @return the horizontal movement
	 */
	public double getHorizontalMovement() {
		return dx;
	}

	/**
	 * Sets the horizontal movement (dx) of the entity.
	 * 
	 * @param dx
	 *            the horizontal movement
	 */
	public void setHorizontalMovement(double dx) {
		this.dx = dx;
	}

	/**
	 * Returns the vertical movement (dy).
	 * 
	 * @return the vertical movement
	 */
	public double getVerticalMovement() {
		return dy;
	}

	/**
	 * Sets the vertical movement (dx) of the entity.
	 * 
	 * @param dy
	 *            the vertical movement
	 */
	public void setVerticalMovement(double dy) {
		this.dy = dy;
	}

	/**
	 * Sets the rotation speed (dangle) of the entity.
	 * 
	 * @param dangle
	 *            the rotation speed
	 */
	public void setRotationSpeed(double dangle) {
		this.dangle = dangle;
	}

	/**
	 * Get the rotation speed (dangle) of the entity.
	 * 
	 * @return the rotation speed
	 */
	public double getRotationSpeed() {
		return dangle;
	}

	/**
	 * Returns the image's width.
	 * 
	 * @return the width
	 */
	public double getWidth() {
		if (scalate) {
			return getImage().getWidth() * scalingFactor;
		} else {
			return getImage().getWidth();
		}
	}

	/**
	 * Returns the image's height.
	 * 
	 * @return the height
	 */
	public double getHeight() {
		if (scalate) {
			return getImage().getHeight() * scalingFactor;
		} else {
			return getImage().getHeight();
		}
	}

	/**
	 * Moves the entity according to the elapsed time.
	 * 
	 * @param milliSecondsElapsed
	 *            the time elapsed in milliseconds
	 */
	public void move(long milliSecondsElapsed) {
		// System.out.println("Milliseconds since last run: " +
		// milliSecondsElapsed);

		x = x + (milliSecondsElapsed * dx);
		y = y + (milliSecondsElapsed * dy);

		angle = angle + (milliSecondsElapsed * dangle);

		if (scalingFactor <= 0.0) {
			scalingFactor = 0.01;
		}

		boundaryPolygon = createBoundaryPolygon(getAffineTransform());
	}

	public double getDx() {
		return dx;
	}

	public void setDx(double dx) {
		this.dx = dx;
	}

	public double getDy() {
		return dy;
	}

	public void setDy(double dy) {
		this.dy = dy;
	}

	/**
	 * Draws the entity on the Graphics2D object.
	 * 
	 * @param g
	 *            the Graphics2D object
	 */
	public void draw(Graphics2D g) {
		AffineTransformOp op =
				new AffineTransformOp(getAffineTransform(),
						AffineTransformOp.TYPE_BILINEAR);
		g.drawImage(getImage(), op, (int) x, (int) y);

		// g.setColor(Color.RED);
		// if(boundaryPolygon != null)
		// g.draw(boundaryPolygon);
	}

	/**
	 * Returns the transformation object for the given scalingFactor and
	 * angle.
	 * 
	 * @return the AffineTransform object
	 */
	private AffineTransform getAffineTransform() {
		AffineTransform at = new AffineTransform();

		if (scalate) {
			at.scale(scalingFactor, scalingFactor);
		}
		if (rotate) {
			at.rotate(angle * Math.PI / 180.0, getImage().getWidth() / 2.0,
					getImage().getHeight() / 2.0);
		}

		return at;
	}

	public void setRotate(boolean rotate) {
		this.rotate = rotate;
	}

	/**
	 * Creates the polygon that represents the boundaries of the entity.
	 * 
	 * @param at
	 *            the transformation object
	 * @return the polygon
	 */
	private Polygon createBoundaryPolygon(AffineTransform at) {
		Polygon polygon = new Polygon();

		Point2D point =
				at.deltaTransform(new Point2D.Double(
						0 - (getImage().getWidth() / 2.0), 0 - (getImage()
								.getHeight() / 2.0)), null);
		polygon.addPoint((int) point.getX(), (int) point.getY());

		point =
				at.deltaTransform(new Point2D.Double(getImage().getWidth()
						- (getImage().getWidth() / 2.0), 0 - (getImage()
						.getHeight() / 2.0)), null);
		polygon.addPoint((int) point.getX(), (int) point.getY());

		point =
				at.deltaTransform(new Point2D.Double(getImage().getWidth()
						- (getImage().getWidth() / 2.0), getImage().getHeight()
						- (getImage().getHeight() / 2.0)), null);
		polygon.addPoint((int) point.getX(), (int) point.getY());

		point =
				at.deltaTransform(new Point2D.Double(
						0 - (getImage().getWidth() / 2.0), getImage()
								.getHeight()
								- (getImage().getHeight() / 2.0)), null);
		polygon.addPoint((int) point.getX(), (int) point.getY());

		if (scalate) {
			polygon.translate(
					(int) (x + (scalingFactor * getImage().getWidth() / 2.0)),
					(int) (y + (scalingFactor * getImage().getHeight() / 2.0)));
		} else {
			polygon.translate((int) (x + (getImage().getWidth() / 2.0)),
					(int) (y + (getImage().getHeight() / 2.0)));
		}

		return polygon;
	}

	/**
	 * Returns the boundaries as Rectangle2D object.
	 * 
	 * @return the Rectangle2D object
	 */
	public Rectangle2D getBoundaries() {
		return boundaryPolygon.getBounds2D();
	}

	/**
	 * Checks if the entity collides with the given rectangle.
	 * 
	 * @param rectangle
	 *            the Rectangle2D object
	 * @return true if there's a collision, false otherwise
	 */
	public boolean collidesWith(Rectangle2D rectangle) {
		if (boundaryPolygon != null) {
			return boundaryPolygon.intersects(rectangle)
					|| boundaryPolygon.contains(rectangle);
		} else {
			return false;
		}

	}

	/**
	 * Checks if the entity collides with the given rectangle.
	 * 
	 * @param sprite
	 *            the sprite/entity to check
	 * @return true if there's a collision, false otherwise
	 */
	public boolean collidesWith(Sprite sprite) {
		Rectangle2D spriteBoundaries = sprite.getBoundaries();
		return collidesWith(spriteBoundaries);
	}

	/**
	 * Is called when the entity collided with another one.
	 * 
	 * @param sprite
	 *            the sprite/entity the entity collided with
	 */
	public abstract void collidedWith(Sprite sprite);
	
	/**
	 * Returns the angle of the sprite.
	 * @return
	 */
	public double getAngle() {
		return angle;
	}
	
	/**
	 * Sets the angle of the sprite.
	 * @param angle
	 */
	public void setAngle(double angle) {
		this.angle = angle;
	}
	
	/**
	 * Returns the id of the sprite.
	 * @return
	 */
	public int getId() {
		return id;
	}

	/**
	 * Sets the id of the sprite.
	 * @param id
	 */
	public void setId(int id) {
		this.id = id;
	}
	
	/**
	 * Returns the image of the sprite.
	 * @return
	 */
	public abstract BufferedImage getImage();

	/**
	 * Clones the object.
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}
